package kz.kstu.aec.curve;

import java.math.BigInteger;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;

/**
 * @author Filipp_Stankevich
 *         created: 25.01.12 17:29
 */
public class EllipticCurveHelper {

    public static ECPoint addPoints(ECPoint p1, ECPoint p2, EllipticCurve curve) {
        if (p1 == ECPoint.POINT_INFINITY) {
            return p2;
        }

        if (p2 == ECPoint.POINT_INFINITY) {
            return p1;
        }

        if (p1.equals(p2)) {
            doublePoint(p1, curve);
        }

        BigInteger p;
        ECField field = curve.getField();
        if (field instanceof ECFieldFp) {
            ECFieldFp ecFieldFp = (ECFieldFp) field;
            p = ecFieldFp.getP();
        } else {
            throw new UnsupportedOperationException("The curve must be contains ECFieldFp object");
        }

        BigInteger dy = p2.getAffineY().subtract(p1.getAffineY());
        BigInteger dx = p2.getAffineX().subtract(p1.getAffineX());

        if (dx.signum() < 0) {
            dx = dx.add(p);
        }
        if (dy.signum() < 0) {
            dy = dy.add(p);
        }

        BigInteger m = dy.multiply(dx.modInverse(p)).remainder(p);
        if (m.signum() < 0) {
            m = m.add(p);
        }

        BigInteger x = m.multiply(m).subtract(p1.getAffineX()).subtract(p2.getAffineX()).remainder(p);
        BigInteger y = m.multiply(p1.getAffineX().subtract(x)).subtract(p1.getAffineY()).remainder(p);

        if (x.signum() < 0) {
            x = x.add(p);
        }
        if (y.signum() < 0) {
            y = y.add(p);
        }
        return new ECPoint(x, y);
    }

    public static ECPoint doublePoint(ECPoint point, EllipticCurve curve) {
        if (point == ECPoint.POINT_INFINITY) {
            return ECPoint.POINT_INFINITY;
        }

        BigInteger p;
        ECField field = curve.getField();
        if (field instanceof ECFieldFp) {
            ECFieldFp ecFieldFp = (ECFieldFp) field;
            p = ecFieldFp.getP();
        } else {
            throw new UnsupportedOperationException("The curve must be contains ECFieldFp object");
        }

        BigInteger dy = point.getAffineX().multiply(point.getAffineX()).multiply(BigInteger.valueOf(3)).add(curve.getA());
        BigInteger dx = point.getAffineY().multiply(BigInteger.valueOf(2));

        if (dx.signum() < 0) {
            dx = dx.add(p);
        }
        if (dy.signum() < 0) {
            dy = dy.add(p);
        }

        BigInteger m = dy.multiply(dx.modInverse(p)).remainder(p);

        BigInteger x = m.multiply(m).subtract(point.getAffineX()).subtract(point.getAffineX()).remainder(p);
        BigInteger y = m.multiply(point.getAffineX().subtract(x)).subtract(point.getAffineY()).remainder(p);

        if (x.signum() < 0) {
            x = x.add(p);
        }
        if (y.signum() < 0) {
            y = y.add(p);
        }

        return new ECPoint(x, y);
    }

    public static ECPoint multiplyPoints(ECPoint point, BigInteger x, EllipticCurve curve) {
        if (point == ECPoint.POINT_INFINITY) {
            return ECPoint.POINT_INFINITY;
        }

        ECPoint resultPoint = ECPoint.POINT_INFINITY;
        String byteString = x.toString(2);
        for (int i = 0; i < byteString.length(); i++) {
            resultPoint = doublePoint(resultPoint, curve);
            if (byteString.charAt(i) == '1') {
                resultPoint = addPoints(resultPoint, point, curve);
            }
        }
        return resultPoint;
    }
}
